package com.api.apigateway.risk;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 滑动窗口计数 & 变化率 & 简单熵近似
 * 运行逻辑：
 * - 在网关内存里维护分钟/5分钟窗口内的 {userId, ak, ip, path} 维度的计数；
 * - 提供 getQps()/getDelta()/getEntropyApprox() 供特征抽取；
 * - 生产上可替换为 Redis（INCR + EXPIRE）或 ClickHouse/TSDB 实时聚合。
 */
public class SlidingWindowStats {
    private final ConcurrentMap<String, Long> minuteCnt = new ConcurrentHashMap<String, Long>();
    private final ConcurrentMap<String, Long> last5MinCnt = new ConcurrentHashMap<String, Long>();
    private final ConcurrentMap<String, Long> lastMinuteCnt = new ConcurrentHashMap<String, Long>();

    /** key 设计：维度拼接，如 "u:{userId}|ak:{ak}|ip:{ip}|p:{pathTpl}" */
    private static String k(String userId, String ak, String ip, String path){
        return "u:" + (userId==null?"-":userId) + "|ak:" + (ak==null?"-":ak) + "|ip:" + (ip==null?"-":ip) + "|p:" + (path==null?"-":path);
    }

    /** 记录一次调用（由 Filter 在每次请求前后调用均可，此处在前置即可） */
    public void mark(Long userId, String ak, String ip, String path){
        String key = k(userId==null?null:String.valueOf(userId), ak, ip, path);
        minuteCnt.merge(key, 1L, new java.util.function.BiFunction<Long, Long, Long>(){
            @Override public Long apply(Long a, Long b){ return Long.valueOf((a==null?0L:a.longValue()) + (b==null?0L:b.longValue())); }
        });
        last5MinCnt.merge(key, 1L, new java.util.function.BiFunction<Long, Long, Long>(){
            @Override public Long apply(Long a, Long b){ return Long.valueOf((a==null?0L:a.longValue()) + (b==null?0L:b.longValue())); }
        });
    }

    /** 获取 1 分钟内 QPS 近似（计数/60） */
    public double getQps(Long userId, String ak, String ip, String path){
        long c = minuteCnt.getOrDefault(k(userId==null?null:String.valueOf(userId), ak, ip, path), 0L);
        return c / 60.0;
    }

    /** 获取 5 分钟 vs 1 分钟的增长率近似（(m1 - mPrev)/max(1,mPrev)） */
    public double getDelta(Long userId, String ak, String ip, String path){
        String key = k(userId==null?null:String.valueOf(userId), ak, ip, path);
        long cur1 = minuteCnt.getOrDefault(key, 0L);
        long prev1 = lastMinuteCnt.getOrDefault(key, 0L);
        // 更新上一分钟基线（简单近似：每次取值时写入）
        lastMinuteCnt.put(key, Long.valueOf(cur1));
        long c5 = last5MinCnt.getOrDefault(key, 0L);
        double avg1 = c5 / 5.0; // 简化：5分钟总数/5 ~ 每分钟均值
        return (cur1 - avg1) / Math.max(1.0, avg1);
    }

    /** 熵近似：以 path 作为类别，统计该 IP 最近 1 分钟调用的路径“多样性”（越集中越低） */
    public double getEntropyApprox(String ip){
        // 为简化：使用 minuteCnt 中同IP不同path的计数近似熵
        // 生产上建议使用滑动窗口桶 + 真实熵计算
        double sum = 0.0;
        java.util.Map<String, Long> buckets = new java.util.HashMap<String, Long>();
        for (java.util.Map.Entry<String, Long> e : minuteCnt.entrySet()){
            if (e.getKey().contains("|ip:" + ip + "|")){
                // 解析 path
                String[] parts = e.getKey().split("\\|p:");
                if (parts.length>=2){
                    String path = parts[1];
                    Long old = buckets.get(path);
                    buckets.put(path, Long.valueOf((old==null?0L:old.longValue()) + e.getValue().longValue()));
                    sum += e.getValue().longValue();
                }
            }
        }
        if (sum <= 0) return 0.0;
        double H = 0.0;
        for (Long c : buckets.values()){
            double p = c / sum;
            H += -p * Math.log(p + 1e-9);
        }
        return H; // 近似熵：越大越分散，越小越集中（集中刷单通常熵很低）
    }

    /** 定时衰减（可用 @Scheduled 每分钟掉一次），避免内存膨胀 */
    public void decay(){
        // 简化：把 minuteCnt / last5MinCnt 都按 0.5 衰减一次
        for (java.util.Map.Entry<String, Long> e : minuteCnt.entrySet()){
            minuteCnt.put(e.getKey(), Long.valueOf(Math.max(0L, e.getValue().longValue()/2)));
        }
        for (java.util.Map.Entry<String, Long> e : last5MinCnt.entrySet()){
            last5MinCnt.put(e.getKey(), Long.valueOf(Math.max(0L, e.getValue().longValue()/2)));
        }
    }
}